/*
 *  Copyright (c) 2008-2009 Mike Massonnet <mmassonnet@xfce.org>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

/*#include <exo/exo.h>*/
#include <gtk/gtk.h>
#include <X11/Xlib.h>
#include <gdk/gdkx.h>
/*#include <libxfcegui4/libxfcegui4.h>
  #include <libxfce4util/libxfce4util.h>*/

#include "collector.h"
#include "history.h"
#include "xfce-fake.h"
#include "menu.h"

/*
 * GObject declarations
 */

G_DEFINE_TYPE (ClipmanMenu, clipman_menu, GTK_TYPE_MENU)

#define GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), CLIPMAN_TYPE_MENU, ClipmanMenuPrivate))

struct _ClipmanMenuPrivate
{
  GtkWidget            *mi_clear_history;
  ClipmanHistory       *history;
  GSList               *list;
};

static void             clipman_menu_class_init         (ClipmanMenuClass *klass);
static void             clipman_menu_init               (ClipmanMenu *menu);
static void             clipman_menu_finalize           (GObject *object);

/*
 * Private methods declarations
 */

static void            _clipman_menu_free_list          (ClipmanMenu *menu);

/*
 * Callbacks declarations
 */

static void             cb_set_clipboard                (const ClipmanHistoryItem *item);
static void             cb_clear_history                (ClipmanMenu *menu);







static gboolean
screen_contains_pointer (GdkScreen *screen,
                         int       *x,
			                   int       *y)
{
	GdkWindow    *root_window;
	Window        root, child;
	Bool          retval;
	int           rootx, rooty;
	int           winx, winy;
	unsigned int  xmask;

	root_window = gdk_screen_get_root_window (screen);

	retval = XQueryPointer (GDK_SCREEN_XDISPLAY (screen),
                          GDK_DRAWABLE_XID (root_window),
                          &root, &child, &rootx, &rooty,
                          &winx, &winy, &xmask);

	if (x)
		*x = retval ? rootx : -1;
	if (y)
		*y = retval ? rooty : -1;

	return retval;
}

/**
 * xfce_gdk_display_locate_monitor_with_pointer:
 * @display        : A Gdk display or %NULL to choose the default display.
 * @monitor_return : Address to store the monitor number to or %NULL.
 *
 * Locates the monitor and the screen which contains the pointer. If
 * it is not possible to determine the current pointer position, %NULL
 * is returned.
 *
 * Return value: The screen that contains the pointer.
 *
 * Since: 4.2
 **/
GdkScreen*
xfce_gdk_display_locate_monitor_with_pointer (GdkDisplay *display,
                                              gint       *monitor_return)
{
	int n_screens, i;

  if (display == NULL)
    display = gdk_display_get_default ();

	n_screens = gdk_display_get_n_screens (display);
	for (i = 0; i < n_screens; i++)
    {
      GdkScreen  *screen;
      int         x, y;

      screen = gdk_display_get_screen (display, i);

      if (screen_contains_pointer (screen, &x, &y))
        {
          if (monitor_return)
            *monitor_return = gdk_screen_get_monitor_at_point (screen, x, y);

          return screen;
	    	}
  	}

	if (monitor_return)
		*monitor_return = 0;

	return NULL;
}





/**
 * xfce_gtk_window_center_on_monitor:
 * @window: The Gtk window to center.
 * @screen: The screen on which the @window should be centered.
 * @monitor: The monitor of the @screen on which @window should be centered.
 *
 * Centers @window on @monitor of @screen. @monitor has to reference a valid
 * monitor of @screen.
 *
 * Since: 4.2
 **/
void
xfce_gtk_window_center_on_monitor (GtkWindow *window,
			                             GdkScreen *screen,
                         			     gint       monitor)
{
	GtkRequisition requisition;
	GdkRectangle   geometry;
        GdkScreen     *widget_screen;
	gint           x, y;

	gdk_screen_get_monitor_geometry (screen, monitor, &geometry);
        
        /* 
         * Getting a size request requires the widget
         * to be associated with a screen, because font
         * information may be needed (Olivier).
         */
        widget_screen = gtk_widget_get_screen (GTK_WIDGET (window));
        if (screen != widget_screen)
          {
            gtk_window_set_screen (GTK_WINDOW (window), screen);
          }
        /*
         * We need to be realized, otherwise we may get 
         * some odd side effects (Olivier). 
         */
        if (!GTK_WIDGET_REALIZED (GTK_WIDGET (window)))
	  {
            gtk_widget_realize (GTK_WIDGET (window));
          }
        /*
         * Yes, I know -1 is useless here (Olivier).
         */
        requisition.width = requisition.height = -1;
	gtk_widget_size_request (GTK_WIDGET (window), &requisition);

	x = geometry.x + (geometry.width - requisition.width) / 2;
	y = geometry.y + (geometry.height - requisition.height) / 2;

        gtk_window_move (window, x, y);
}


/**
 * xfce_gtk_window_center_on_monitor_with_pointer:
 * @window: The Gtk window to center.
 *
 * Determines the monitor that contains the pointer and centers the
 * @window on it. If it failes to determine the current pointer position,
 * @window is centered on the first monitor of the default screen on
 * the default display.
 */
void
xfce_gtk_window_center_on_monitor_with_pointer (GtkWindow *window)
{
  GdkScreen *screen;
  gint       monitor;

  screen = xfce_gdk_display_locate_monitor_with_pointer (NULL, &monitor);
  if (screen == NULL)
    {
      screen = gdk_screen_get_default ();
      monitor = 0;
    }

  xfce_gtk_window_center_on_monitor (window, screen, monitor);
}





/**
 * xfce_create_mixed_button:
 * @stock: a stock item name.
 * @text: a text to display.
 *
 * Creates a button with both @stock icon and @text.
 *
 * Return value: the newly created mixed button widget.
 **/
static GtkWidget*
xfce_create_mixed_button (const gchar *stock, const gchar *text)
{
    GtkWidget *button, *align, *image, *hbox, *label;

    button = gtk_button_new ();
    label = gtk_label_new_with_mnemonic (text);
    gtk_label_set_mnemonic_widget (GTK_LABEL (label), button);

    image = gtk_image_new_from_stock (stock, GTK_ICON_SIZE_BUTTON);
    hbox = gtk_hbox_new (FALSE, 2);

    align = gtk_alignment_new (0.5, 0.5, 0.0, 0.0);

    gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0);
    gtk_box_pack_end (GTK_BOX (hbox), label, FALSE, FALSE, 0);

    gtk_container_add (GTK_CONTAINER (button), align);
    gtk_container_add (GTK_CONTAINER (align), hbox);
    gtk_widget_show_all (align);

    return button;
}


/* compat stub */
static GtkWidget*
mixed_button_new (const char *stock, const char *text)
{
    return xfce_create_mixed_button (stock, text);
}




static gboolean
xfce_confirm (const gchar *text,
              const gchar *stock_id,
              const gchar *action)
{
    GtkWidget *dialog, *button;
    int response = GTK_RESPONSE_NONE;

    dialog = gtk_message_dialog_new (NULL,
                                     GTK_DIALOG_MODAL,
                                     GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE,
                                     "%s", text);
    if (strcmp (stock_id, GTK_STOCK_YES) == 0)
        button = gtk_button_new_from_stock (GTK_STOCK_NO);
    else
        button = gtk_button_new_from_stock (GTK_STOCK_CANCEL);

    gtk_widget_show (button);
    gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button,
                                  GTK_RESPONSE_NO);

    if (action)
        button = mixed_button_new (stock_id, action);
    else
        button = gtk_button_new_from_stock (stock_id);
    gtk_widget_show (button);
    gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button,
                                  GTK_RESPONSE_YES);

    gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_CENTER);

    xfce_gtk_window_center_on_monitor_with_pointer (GTK_WINDOW (dialog));
    response = gtk_dialog_run (GTK_DIALOG (dialog));
    gtk_widget_hide (dialog);
    gtk_widget_destroy (dialog);

    if (response == GTK_RESPONSE_YES)
        return TRUE;
    else
        return FALSE;
}







/*
 * Callbacks
 */

static void
cb_set_clipboard (const ClipmanHistoryItem *item)
{
  GtkClipboard *clipboard;
  ClipmanCollector *collector;
  ClipmanHistory *history;

  switch (item->type)
    {
    case CLIPMAN_HISTORY_TYPE_TEXT:
      clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
      gtk_clipboard_set_text (clipboard, item->content.text, -1);

      clipboard = gtk_clipboard_get (GDK_SELECTION_PRIMARY);
      gtk_clipboard_set_text (clipboard, item->content.text, -1);
      break;

    case CLIPMAN_HISTORY_TYPE_IMAGE:
      DBG ("Copy image (%p) to default clipboard", item->content.image);

      collector = clipman_collector_get ();
      clipman_collector_set_is_restoring (collector);
      g_object_unref (collector);

      history = clipman_history_get ();
      clipman_history_set_item_to_restore (history, item);
      g_object_unref (history);

      clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
      gtk_clipboard_set_image (clipboard, GDK_PIXBUF (item->content.image));
      break;

    default:
      g_assert_not_reached ();
    }
}

static void
cb_clear_history (ClipmanMenu *menu)
{
  GtkClipboard *clipboard;

  if (!xfce_confirm (_("Are you sure you want to clear the history?"), GTK_STOCK_YES, NULL))
    return;

  clipman_history_clear (menu->priv->history);

  clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD);
  gtk_clipboard_clear (clipboard);

  clipboard = gtk_clipboard_get (GDK_SELECTION_PRIMARY);
  gtk_clipboard_clear (clipboard);
}

/*
 * Private methods
 */

static void
_clipman_menu_update_list (ClipmanMenu *menu)
{
  GtkWidget *mi, *image;
  ClipmanHistoryItem *item;
  const ClipmanHistoryItem *item_to_restore;
  GSList *list, *l;
  gint pos = 0;

  /* Get the most recent item in the history */
  item_to_restore = clipman_history_get_item_to_restore (menu->priv->history);

  /* Clear the previous menu items */
  _clipman_menu_free_list (menu);

  /* Set the clear history item sensitive */
  gtk_widget_set_sensitive (menu->priv->mi_clear_history, TRUE);

  /* Insert an updated list of menu items */
  list = clipman_history_get_list (menu->priv->history);
  for (l = list; l != NULL; l = l->next)
    {
      item = l->data;

      switch (item->type)
        {
        case CLIPMAN_HISTORY_TYPE_TEXT:
          mi = gtk_image_menu_item_new_with_label (item->preview.text);
          g_signal_connect_swapped (mi, "activate", G_CALLBACK (cb_set_clipboard), item);
          break;

        case CLIPMAN_HISTORY_TYPE_IMAGE:
          mi = gtk_image_menu_item_new ();
          image = gtk_image_new_from_pixbuf (item->preview.image);
          gtk_container_add (GTK_CONTAINER (mi), image);
          g_signal_connect_swapped (mi, "activate", G_CALLBACK (cb_set_clipboard), item);
          break;

        default:
          g_assert_not_reached ();
        }

      if (item == item_to_restore)
        {
          image = gtk_image_new_from_stock (GTK_STOCK_GO_FORWARD, GTK_ICON_SIZE_MENU);
          gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (mi), image);
        }

      menu->priv->list = g_slist_prepend (menu->priv->list, mi);
      gtk_menu_shell_insert (GTK_MENU_SHELL (menu), mi, pos++);
      gtk_widget_show_all (mi);
    }
  g_slist_free (list);

  if (pos == 0)
    {
      /* Insert empty menu item */
      mi = gtk_menu_item_new_with_label (_("Clipboard is empty"));
      menu->priv->list = g_slist_prepend (menu->priv->list, mi);
      gtk_menu_shell_insert (GTK_MENU_SHELL (menu), mi, 0);
      gtk_widget_set_sensitive (mi, FALSE);
      gtk_widget_show (mi);

      /* Set the clear history item insensitive */
      gtk_widget_set_sensitive (menu->priv->mi_clear_history, FALSE);
    }
}

static void
_clipman_menu_free_list (ClipmanMenu *menu)
{
  GSList *list;
  for (list = menu->priv->list; list != NULL; list = list->next)
    gtk_widget_destroy (GTK_WIDGET (list->data));
  g_slist_free (menu->priv->list);
  menu->priv->list = NULL;
}

/*
 * Public methods
 */

GtkWidget *
clipman_menu_new ()
{
  return g_object_new (CLIPMAN_TYPE_MENU, NULL);
}

/*
 * GObject
 */

static void
clipman_menu_class_init (ClipmanMenuClass *klass)
{
  GObjectClass *object_class;

  g_type_class_add_private (klass, sizeof (ClipmanMenuPrivate));

  clipman_menu_parent_class = g_type_class_peek_parent (klass);

  object_class = G_OBJECT_CLASS (klass);
  object_class->finalize = clipman_menu_finalize;
}

static void
clipman_menu_init (ClipmanMenu *menu)
{
  GtkWidget *mi;
  ClipmanCollector *collector;

  menu->priv = GET_PRIVATE (menu);

  /* ClipmanHistory */
  menu->priv->history = clipman_history_get ();

  /* Connect signal on show to update the items */
  g_signal_connect_swapped (menu, "show", G_CALLBACK (_clipman_menu_update_list), menu);

  /* Footer items */
  mi = gtk_separator_menu_item_new ();
  gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi);

  menu->priv->mi_clear_history = mi = gtk_image_menu_item_new_from_stock (GTK_STOCK_CLEAR, NULL);
  gtk_menu_shell_append (GTK_MENU_SHELL (menu), mi);
  g_signal_connect_swapped (mi, "activate", G_CALLBACK (cb_clear_history), menu);

  /* Show all the items */
  gtk_widget_show_all (GTK_WIDGET (menu));
}

static void
clipman_menu_finalize (GObject *object)
{
  _clipman_menu_free_list (CLIPMAN_MENU (object));
  G_OBJECT_CLASS (clipman_menu_parent_class)->finalize (object);
}

